#ifndef __BV_BOX_H__
#define __BV_BOX_H__

namespace inf
{
    /*
    ===============================================================================

    Oriented Bounding Box

    ===============================================================================
    */

    class Bounds;

    class Box {
    public:
        Box( void );
        explicit Box( const Vec3 &center, const Vec3 &extents, const Mat3 &axis );
        explicit Box( const Vec3 &point );
        explicit Box( const Bounds &bounds );
        explicit Box( const Bounds &bounds, const Vec3 &origin, const Mat3 &axis );

        Box			operator+( const Vec3 &t ) const;				// returns translated box
        Box &			operator+=( const Vec3 &t );					// translate the box
        Box			operator*( const Mat3 &r ) const;				// returns rotated box
        Box &			operator*=( const Mat3 &r );					// rotate the box
        Box			operator+( const Box &a ) const;
        Box &			operator+=( const Box &a );
        Box			operator-( const Box &a ) const;
        Box &			operator-=( const Box &a );

        bool			compare( const Box &a ) const;						// exact compare, no epsilon
        bool			compare( const Box &a, const float epsilon ) const;	// compare with epsilon
        bool			operator==(	const Box &a ) const;						// exact compare, no epsilon
        bool			operator!=(	const Box &a ) const;						// exact compare, no epsilon

        void			Clear( void );									// inside out box
        void			zero( void );									// single point at origin

        const Vec3 &	GetCenter( void ) const;						// returns center of the box
        const Vec3 &	GetExtents( void ) const;						// returns extents of the box
        const Mat3 &	GetAxis( void ) const;							// returns the axis of the box
        float			GetVolume( void ) const;						// returns the volume of the box
        bool			IsCleared( void ) const;						// returns true if box are inside out

        bool			AddPoint( const Vec3 &v );					// add the point, returns true if the box expanded
        bool			AddBox( const Box &a );						// add the box, returns true if the box expanded
        Box			    Expand( const float d ) const;					// return box expanded in all directions with the given value
        Box &			ExpandSelf( const float d );					// expand box in all directions with the given value
        Box			    Translate( const Vec3 &translation ) const;	// return translated box
        Box &			TranslateSelf( const Vec3 &translation );		// translate this box
        Box			    Rotate( const Mat3 &rotation ) const;			// return rotated box
        Box &			RotateSelf( const Mat3 &rotation );			// rotate this box

        float			PlaneDistance( const Plane &plane ) const;
        int				PlaneSide( const Plane &plane, const float epsilon = math::FLOAT_EPSILON ) const;

        bool			ContainsPoint( const Vec3 &p ) const;			// includes touching
        bool			IntersectsBox( const Box &a ) const;			// includes touching
        bool			LineIntersection( const Vec3 &start, const Vec3 &end ) const;
        // intersection points are (start + dir * scale1) and (start + dir * scale2)
        bool			RayIntersection( const Vec3 &start, const Vec3 &dir, float &scale1, float &scale2 ) const;

        // tight box for a collection of points
        //void			FromPoints( const Vec3 *points, const int numPoints );
        // most tight box for a translation
        void			FromPointTranslation( const Vec3 &point, const Vec3 &translation );
        void			FromBoxTranslation( const Box &box, const Vec3 &translation );
        // most tight box for a rotation
        void			FromPointRotation( const Vec3 &point, const Rotation &rotation );
        void			FromBoxRotation( const Box &box, const Rotation &rotation );

        void			ToPoints( Vec3 points[8] ) const;
        Sphere		ToSphere( void ) const;

        // calculates the projection of this box onto the given axis
        void			AxisProjection( const Vec3 &dir, float &min, float &max ) const;
        void			AxisProjection( const Mat3 &ax, Bounds &bounds ) const;

        // calculates the silhouette of the box
        int				GetProjectionSilhouetteVerts( const Vec3 &projectionOrigin, Vec3 silVerts[6] ) const;
        int				GetParallelProjectionSilhouetteVerts( const Vec3 &projectionDir, Vec3 silVerts[6] ) const;

    private:
        Vec3			center;
        Vec3			extents;
        Mat3			axis;
    };

    extern Box	box_zero;

    INF_INLINE Box::Box( void ) {
    }

    INF_INLINE Box::Box( const Vec3 &center, const Vec3 &extents, const Mat3 &axis ) {
        this->center = center;
        this->extents = extents;
        this->axis = axis;
    }

    INF_INLINE Box::Box( const Vec3 &point ) {
        this->center = point;
        this->extents.zero();
        this->axis.identity();
    }

    INF_INLINE Box::Box( const Bounds &bounds ) {
        this->center = ( bounds[0] + bounds[1] ) * 0.5f;
        this->extents = bounds[1] - this->center;
        this->axis.identity();
    }

    INF_INLINE Box::Box( const Bounds &bounds, const Vec3 &origin, const Mat3 &axis ) {
        this->center = ( bounds[0] + bounds[1] ) * 0.5f;
        this->extents = bounds[1] - this->center;
        this->center = origin + this->center * axis;
        this->axis = axis;
    }

    INF_INLINE Box Box::operator+( const Vec3 &t ) const {
        return Box( center + t, extents, axis );
    }

    INF_INLINE Box &Box::operator+=( const Vec3 &t ) {
        center += t;
        return *this;
    }

    INF_INLINE Box Box::operator*( const Mat3 &r ) const {
        return Box( center * r, extents, axis * r );
    }

    INF_INLINE Box &Box::operator*=( const Mat3 &r ) {
        center *= r;
        axis *= r;
        return *this;
    }

    INF_INLINE Box Box::operator+( const Box &a ) const {
        Box newBox;
        newBox = *this;
        newBox.AddBox( a );
        return newBox;
    }

    INF_INLINE Box &Box::operator+=( const Box &a ) {
        Box::AddBox( a );
        return *this;
    }

    INF_INLINE Box Box::operator-( const Box &a ) const {
        return Box( center, extents - a.extents, axis );
    }

    INF_INLINE Box &Box::operator-=( const Box &a ) {
        extents -= a.extents;
        return *this;
    }

    INF_INLINE bool Box::compare( const Box &a ) const {
        return ( center.compare( a.center ) && extents.compare( a.extents ) && axis.compare( a.axis ) );
    }

    INF_INLINE bool Box::compare( const Box &a, const float epsilon ) const {
        return ( center.compare( a.center, epsilon ) && extents.compare( a.extents, epsilon ) && axis.compare( a.axis, epsilon ) );
    }

    INF_INLINE bool Box::operator==( const Box &a ) const {
        return compare( a );
    }

    INF_INLINE bool Box::operator!=( const Box &a ) const {
        return !compare( a );
    }

    INF_INLINE void Box::Clear( void ) {
        center.zero();
        extents[0] = extents[1] = extents[2] = -math::INFINITY;
        axis.identity();
    }

    INF_INLINE void Box::zero( void ) {
        center.zero();
        extents.zero();
        axis.identity();
    }

    INF_INLINE const Vec3 &Box::GetCenter( void ) const {
        return center;
    }

    INF_INLINE const Vec3 &Box::GetExtents( void ) const {
        return extents;
    }

    INF_INLINE const Mat3 &Box::GetAxis( void ) const {
        return axis;
    }

    INF_INLINE float Box::GetVolume( void ) const {
        return ( extents * 2.0f ).length_sqr();
    }

    INF_INLINE bool Box::IsCleared( void ) const {
        return extents[0] < 0.0f;
    }

    INF_INLINE Box Box::Expand( const float d ) const {
        return Box( center, extents + Vec3( d, d, d ), axis );
    }

    INF_INLINE Box &Box::ExpandSelf( const float d ) {
        extents[0] += d;
        extents[1] += d;
        extents[2] += d;
        return *this;
    }

    INF_INLINE Box Box::Translate( const Vec3 &translation ) const {
        return Box( center + translation, extents, axis );
    }

    INF_INLINE Box &Box::TranslateSelf( const Vec3 &translation ) {
        center += translation;
        return *this;
    }

    INF_INLINE Box Box::Rotate( const Mat3 &rotation ) const {
        return Box( center * rotation, extents, axis * rotation );
    }

    INF_INLINE Box &Box::RotateSelf( const Mat3 &rotation ) {
        center *= rotation;
        axis *= rotation;
        return *this;
    }

    INF_INLINE bool Box::ContainsPoint( const Vec3 &p ) const {
        Vec3 lp = p - center;
        if ( math::fabs( lp * axis[0] ) > extents[0] ||
            math::fabs( lp * axis[1] ) > extents[1] ||
            math::fabs( lp * axis[2] ) > extents[2] ) {
                return false;
        }
        return true;
    }

    INF_INLINE Sphere Box::ToSphere( void ) const {
        return Sphere( center, extents.length() );
    }

    INF_INLINE void Box::AxisProjection( const Vec3 &dir, float &min, float &max ) const {
        float d1 = dir * center;
        float d2 = math::fabs( extents[0] * ( dir * axis[0] ) ) +
            math::fabs( extents[1] * ( dir * axis[1] ) ) +
            math::fabs( extents[2] * ( dir * axis[2] ) );
        min = d1 - d2;
        max = d1 + d2;
    }

    INF_INLINE void Box::AxisProjection( const Mat3 &ax, Bounds &bounds ) const {
        for ( int i = 0; i < 3; i++ ) {
            float d1 = ax[i] * center;
            float d2 = math::fabs( extents[0] * ( ax[i] * axis[0] ) ) +
                math::fabs( extents[1] * ( ax[i] * axis[1] ) ) +
                math::fabs( extents[2] * ( ax[i] * axis[2] ) );
            bounds[0][i] = d1 - d2;
            bounds[1][i] = d1 + d2;
        }
    }

}   // ::inf

#endif /* !__BV_BOX_H__ */
